package com.calabar.query;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.calabar.enum_package.Filling;
import com.calabar.enum_package.SampleFunc;
import org.apache.commons.lang3.StringUtils;
import com.calabar.util.HttpClientPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.util.*;

/**
 * <p/>
 * <li>Description: 历史查询实现类</li>
 * <li>@author: zijian.wu</li>
 * <li>Date: 2018/4/8 16:41</li>
 */

@Repository
public class HistoryQuery implements IHistoryQuery {

    /**
     * 查询子系统编码的对象
     */
    @Autowired
    private QuerySubStdCode querySubStdCode;

    /**
     * opentsdb的URL配置文件获取
     */
    @Value("${opentsdb.url.query}")
    private String URL;

    /**
     * 时序数据库的PLANT plt_code
     */
    private final static String PLANT_TAG_KEY = "plt_code";

    /**
     * 时序数据库的SET set_code
     */
    private final static String SET_TAG_KEY = "set_code";

    /**
     * 时序数据库存储的原始值后缀
     */
    private final static String VALUE_SUFFIX = "_src_value";

    /**
     * 时序数据库存储的原始质量标签后缀
     */
    private final static String QUALITY_SUFFIX = "_src_data_quality";

    /**
     * 时序数据库的空值
     */
    private final static String NAN = "NaN";

    /**
     * 时序数据库的dps冒号分割符
     */
    private final static String DPS_SPLIT_SYMBOL = ":";

    /**
     * @param stdCodes       测点标准编码
     * @param pltCode        电厂编码
     * @param startTime      起始时间
     * @param endTime        结束时间
     * @param filling        插值方式（不做插值，线性，取前）
     * @param sampleInterval 采样间隔（单位：秒）
     * @param func           采样函数（AVG,MIN, MAX,OPEN,CLOSE;）
     * @return Json字符串结果
     * @throws Exception 参数异常,请求异常
     */
    @Override
    public String historyQuery(List<String> stdCodes, String pltCode, String setCode, long startTime, long endTime, Filling filling,
                               int sampleInterval, SampleFunc func) throws Exception {

        //根据起止时间确定是查询历史点还是历史区间
        if (startTime == endTime) {
            return historyQuerySinglePoint(stdCodes, pltCode, setCode, startTime, filling);
        } else {
            return historyQueryInterval(stdCodes, pltCode, setCode, startTime, endTime, filling, sampleInterval, func);
        }
    }

    /**
     * 历史数据点查询
     *
     * @param stdCodes  测点标准编码
     * @param pltCode   电厂编码
     * @param setCode   机组编码
     * @param timestamp 时间戳
     * @param filling   插值填充方法
     * @return 查询结果json字符串
     * @throws Exception 参数异常，请求异常
     */
    public String historyQuerySinglePoint(List<String> stdCodes, String pltCode, String setCode, long timestamp, Filling filling) throws Exception {

        //查询子系统编码
        Map<String, String> subMapStd = querySubStdCode.findSubMapStd(stdCodes);
        Map<String, String> StdMapsub = querySubStdCode.findStdMapSub(stdCodes);
        List<String> subStdCodes = new ArrayList<>();
        for (String subStdCode : subMapStd.keySet()) {
            subStdCodes.add(subStdCode);
        }
        //测点加上质量标签后缀
        List<String> subStdCodesQualities = new ArrayList();
        for (String subStdCode : subStdCodes) {
            String subStdCodeQuality = subStdCode + QUALITY_SUFFIX;
            subStdCodesQualities.add(subStdCodeQuality);
        }

        //查询范围加大用于插值
        long startTime = timestamp - 1;
        long endTime = timestamp + 1;

        //标签（电厂编码）
        Map<String, String> tag = new HashMap<>();
        tag.put(PLANT_TAG_KEY, pltCode);
        tag.put(SET_TAG_KEY, setCode);

        //查询质量标签
        List<Map<String, Object>> qualityQueriesList = new ArrayList<>();
        for (String subStdCodeQulity : subStdCodesQualities) {
            //质量标签
            Map<String, Object> qualityQuery = new HashMap<>();
            qualityQuery.put("metric", subStdCodeQulity);
            qualityQuery.put("tags", tag);
            qualityQuery.put("aggregator", "none");
            qualityQueriesList.add(qualityQuery);
        }
        //剩余查询条件
        Map<String, Object> query = new HashMap<>();
        query.put("queries", qualityQueriesList);
        query.put("start", startTime);
        query.put("end", endTime);

        //查询条件转为json字符串
        String queriesJson = JSON.toJSONString(query, SerializerFeature.DisableCircularReferenceDetect);

        //质量标签查询请求
        String qualitiesResult = HttpClientPost.post(URL, queriesJson);

        //如果质量标签查询结果不为空准备进行结果数据组装
        JSONArray qualitiesResultsArr = null;
        if (StringUtils.isNotEmpty(qualitiesResult)) {
            qualitiesResultsArr = JSON.parseArray(qualitiesResult);
        }

        //通过历史间隔查询
        String intervalResults = this.historyQueryInterval(stdCodes, pltCode, setCode, startTime, endTime, filling, 1, SampleFunc.AVG);

        //如果质量标签查询结果不为空，进行结果数据组装
        String resultsStr = null;
        if (StringUtils.isNotEmpty(intervalResults)) {
            JSONObject jsonObject = JSON.parseObject(intervalResults);
            jsonObject.remove("sampleInterval");
            jsonObject.remove("sampleFunc");
            jsonObject.put("startTime", timestamp);
            jsonObject.put("endTime", timestamp);
            JSONArray resultsArr = jsonObject.getJSONArray("results");
            //转换数组为空判断
            if (resultsArr != null && resultsArr.size() > 0) {
                List<JSONObject> resultsList = new ArrayList();
                for (int i = 0; i < resultsArr.size(); i++) {
                    JSONObject singleData = resultsArr.getJSONObject(i);
                    Object value = singleData.getJSONObject("values").get(Long.toString(timestamp));
                    singleData.put("values", value);

                    //更改测点编码，去掉后缀
                    String subStdCode = StdMapsub.get(singleData.getString("stdCode").split("_")[0]);
                    //子系统编码映射标准编码加入返回结果
                    singleData.put("stdCode", subMapStd.get(subStdCode));
                    //先将质量标签拼入
                    singleData.put("quality", "NaN");
                    //结果加入质量标签
                    if (qualitiesResultsArr != null && qualitiesResultsArr.size() > 0) {
                        for (int j = 0; j < qualitiesResultsArr.size(); j++) {
                            JSONObject singleDataQuality = qualitiesResultsArr.getJSONObject(j);
                            //取测点编码相同的质量标签
                            if (subStdCode.equals(singleDataQuality.getString("metric").split("_")[0])) {
                                Object quality = singleDataQuality.getJSONObject("dps").get(Long.toString(timestamp));
                                if (quality != null) {
                                    singleData.put("quality", quality);
                                }
                                break;
                            }
                        }
                    }
                    resultsList.add(singleData);
                }
                jsonObject.put("results", resultsList);
            }
            Map<String, Object> resultsMap = new LinkedHashMap<>(jsonObject);
            resultsStr = JSON.toJSONString(resultsMap);
        }
        return resultsStr;
    }


//    public String historyQuerySingle(List<String> stdCodes, String pltCode, long timestamp, Filling filling,
//                                     int sampleInterval, SampleFunc func) throws Exception {
//        //判断参数
//        sampleInterval = sampleInterval <= 0 ? 1 : sampleInterval;
//        //差值
//        long difference = (timestamp % sampleInterval) == 0 ? sampleInterval
//                : ((timestamp % sampleInterval) + sampleInterval);
//
//        long startTime = timestamp - difference;
//        long endTime = timestamp + difference;
//
//        Map<String, String> tag = new HashMap<>();
//        tag.put(TAG_KEY, pltCode);
//
//        List<Map<String, Object>> queriesList = new ArrayList();
//        for (int i = 0; i < stdCodes.size(); i++) {
//            Map<String, Object> query = new HashMap<>();
//            query.put("metric", stdCodes.get(i));
//            query.put("tags", tag);
//            query.put("downsample", sampleInterval + "s-" + func.getFunc() + "-nan");
//            query.put("aggregator", "none");
//            queriesList.add(query);
//        }
//        Map<String, Object> queryJson = new HashMap<>();
//        queryJson.put("queries", queriesList);
//        queryJson.put("start", startTime);
//        queryJson.put("end", endTime);
//
//
//        String queriesJson = JSON.toJSONString(queryJson, SerializerFeature.DisableCircularReferenceDetect);
//        System.out.println(queriesJson);
//        String result = HttpClientPost.post(URL, queriesJson);
//
//        //组装数据
//
//        String resultsStr = null;
//        if (StringUtils.isNotEmpty(result)) {
//
//            Map<String, Object> returnData = new LinkedHashMap<>();
//            returnData.put(TAG_KEY, pltCode);
//            returnData.put("filling", filling);
//            returnData.put("sample_interval", sampleInterval);
//            returnData.put("sample_func", func);
//            returnData.put("start_time", timestamp);
//            returnData.put("end_time", timestamp);
//            returnData.put("sample_interval", sampleInterval);
//
//            //转为json数组
//            JSONArray jsonArr = JSON.parseArray(result);
//
//            List<Map<String, Object>> results = new ArrayList<>();
//
//            for (int i = 0; i < jsonArr.size(); i++) {
//                Map<String, Object> singleCode = new LinkedHashMap<>();
//                JSONObject jsonObject = jsonArr.getJSONObject(i);
//                singleCode.put("std_code", jsonObject.get("metric").toString());
//
//                //插值
//                Map<String, Object> dataPoints = valuesInterpolation(jsonObject.getJSONObject("dps"), filling);
//                singleCode.put("values", dataPoints);
//
//                results.add(singleCode);
//            }
//            returnData.put("results", results);
//            resultsStr = JSON.toJSONString(returnData);
//        }
//
//        return resultsStr;
//    }

    /**
     * 历史时间段查询
     *
     * @param stdCodes       测点标准编码
     * @param pltCode        电厂编码
     * @param setCode        机组编码
     * @param startTime      起始时间
     * @param endTime        结束时间
     * @param filling        插值填充方法
     * @param sampleInterval 采样间隔
     * @param func           采样函数
     * @return 查询结果json字符串
     * @throws Exception 参数异常，请求异常
     */
    public String historyQueryInterval(List<String> stdCodes, String pltCode, String setCode, long startTime, long endTime,
                                       Filling filling, int sampleInterval, SampleFunc func) throws Exception {

        //查询子系统编码
        Map<String, String> subMapStd = querySubStdCode.findSubMapStd(stdCodes);
        List<String> subStdCodes = new ArrayList<>();
        for (String subStdCode : subMapStd.keySet()) {
            subStdCodes.add(subStdCode);
        }
        //测点加上值后缀
        List<String> subStdCodesValues = new ArrayList();
        for (String subStdCode : subStdCodes) {
            String subStdCodeValue = subStdCode + VALUE_SUFFIX;
            subStdCodesValues.add(subStdCodeValue);
        }


        //组装查询条件
        Map<String, String> tag = new HashMap<>();
        tag.put(PLANT_TAG_KEY, pltCode);
        tag.put(SET_TAG_KEY, setCode);

        List<Map<String, Object>> queriesList = new ArrayList();
        for (int i = 0; i < subStdCodesValues.size(); i++) {
            Map<String, Object> query = new HashMap<>();
            query.put("metric", subStdCodesValues.get(i));
            query.put("tags", tag);
            query.put("downsample", sampleInterval + "s-" + func.getFunc() + "-nan");
            query.put("aggregator", "none");
            queriesList.add(query);
        }
        Map<String, Object> queryJson = new HashMap<>();
        queryJson.put("queries", queriesList);
        queryJson.put("start", startTime);
        queryJson.put("end", endTime);

        //查询条件转为json字符串
        String queriesJson = JSON.toJSONString(queryJson, SerializerFeature.DisableCircularReferenceDetect);

        //请求查询返回结果
        String result = HttpClientPost.post(URL, queriesJson);

        //组装数据
        String resultsStr = null;
        if (StringUtils.isNotEmpty(result)) {

            Map<String, Object> returnData = new LinkedHashMap<>();
            returnData.put("pltCode", pltCode);
            returnData.put("setCode", setCode);
            returnData.put("filling", filling);
            returnData.put("sampleInterval", sampleInterval);
            returnData.put("sampleFunc", func);
            returnData.put("startTime", startTime);
            returnData.put("endTime", endTime);
            returnData.put("sampleInterval", sampleInterval);

            //转为json数组
            JSONArray jsonArr = JSON.parseArray(result);

            List<Map<String, Object>> results = new ArrayList<>();

            for (int i = 0; i < jsonArr.size(); i++) {
                Map<String, Object> singleCode = new LinkedHashMap<>();
                JSONObject jsonObject = jsonArr.getJSONObject(i);
                String subStdCode = jsonObject.get("metric").toString().split("_")[0];
                singleCode.put("stdCode", subMapStd.get(subStdCode));


                //插值
                Map<String, Object> dataPoints = valuesInterpolation(jsonObject.getJSONObject("dps"), filling);
                singleCode.put("values", dataPoints);

                results.add(singleCode);
            }
            returnData.put("results", results);
            resultsStr = JSON.toJSONString(returnData);
        }

        return resultsStr;
    }

    /**
     * 插值
     *
     * @param dps     需要插值的测点值（时间戳:值）
     * @param filling 插值填充方法
     * @return 插值完成的map
     */
    public static Map<String, Object> valuesInterpolation(JSONObject dps, Filling filling) {
        //测点数据TreeMap
        Map<String, Object> dpsTreeMap = new TreeMap<>(dps);
        //将Map里的数据放入List进行处理，声明List
        List<String> allData = new ArrayList<>();

        //循环将Map里的数据放入List进行处理
        for (Map.Entry entry : dpsTreeMap.entrySet()) {
            allData.add(entry.getKey().toString() + DPS_SPLIT_SYMBOL + entry.getValue().toString());
        }

        //线性插值处理中间数据
        if (Filling.LERP.getCode() == filling.getCode()) {

            for (int i = 1; i < allData.size() - 1; i++) {
                if (NAN.equals(allData.get(i).split(DPS_SPLIT_SYMBOL)[1])) {
                    long nowTime = Long.parseLong(allData.get(i).split(DPS_SPLIT_SYMBOL)[0].toString());
                    if (!NAN.equals(allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[1])
                            && !NAN.equals(allData.get(i + 1).split(DPS_SPLIT_SYMBOL)[1])) {
                        long lastTime = Long.parseLong(allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[0].toString());
                        BigDecimal lastValue = new BigDecimal(allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[1].toString());
                        long nextTime = Long.parseLong(allData.get(i + 1).split(DPS_SPLIT_SYMBOL)[0].toString());
                        BigDecimal nextValue = new BigDecimal(allData.get(i + 1).split(DPS_SPLIT_SYMBOL)[1].toString());
                        BigDecimal fillingValue = LERPInterpolation(nowTime, lastTime, nextTime, lastValue, nextValue);
                        allData.set(i, nowTime + DPS_SPLIT_SYMBOL + fillingValue);
                        dpsTreeMap.put(Long.toString(nowTime), fillingValue);
                    } else {
                        allData.set(i, nowTime + DPS_SPLIT_SYMBOL + allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[1].toString());
                        Object lastValue = allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[1];
                        if (!NAN.equals(lastValue.toString())) {
                            lastValue = Float.parseFloat(allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[1]);
                        }
                        dpsTreeMap.put(Long.toString(nowTime), lastValue);
                    }
                }
            }
        }
        //取前处理中间数据
        if (Filling.PRE.getCode() == filling.getCode()) {
            for (int i = 1; i < allData.size() - 1; i++) {
                if (NAN.equals(allData.get(i).split(DPS_SPLIT_SYMBOL)[1])) {
                    long nowTime = Long.parseLong(allData.get(i).split(DPS_SPLIT_SYMBOL)[0].toString());
                    Object lastValue = allData.get(i - 1).split(DPS_SPLIT_SYMBOL)[1];
                    if (!NAN.equals(lastValue.toString())) {
                        lastValue = new BigDecimal(lastValue.toString());
                    }
                    allData.set(i, nowTime + DPS_SPLIT_SYMBOL + lastValue);
                    dpsTreeMap.put(Long.toString(nowTime), lastValue);
                }
            }

        }
        //处理尾部数据
        if (Filling.PRE.getCode() == filling.getCode() || Filling.LERP.getCode() == filling.getCode()) {
            //如果为空取上一个值取
            if (NAN.equals(allData.get(allData.size() - 1).split(DPS_SPLIT_SYMBOL)[1])) {
                long nowTime = Long.parseLong(allData.get((allData.size() - 1)).split(DPS_SPLIT_SYMBOL)[0].toString());
                Object lastValue = allData.get(allData.size() - 2).split(DPS_SPLIT_SYMBOL)[1];
                if (!NAN.equals(lastValue.toString())) {
                    lastValue = new BigDecimal(lastValue.toString());
                }
                dpsTreeMap.put(Long.toString(nowTime), lastValue);
            }
        }
        //清空list，释放内存
        allData.clear();

        return dpsTreeMap;

    }

    /**
     * 线性插值公式（ 即：y = (x - x0) * (y1 - y0) / (x1 - x0) + y0 ）
     *
     * @param x  插值点时间戳
     * @param x0 插值点上一个点的时间戳
     * @param x1 插值点下一个点的时间戳
     * @param y0 插值点上一个点的值
     * @param y1 插值点下一个点的值
     * @return 插值
     */
    public static BigDecimal LERPInterpolation(long x, long x0, long x1, BigDecimal y0, BigDecimal y1) {

        BigDecimal xDifferenceValue = new BigDecimal(x - x0);
        BigDecimal yDifference = y1.subtract(y0);
        BigDecimal xDifference = new BigDecimal(x1 - x0);
        BigDecimal y = xDifferenceValue.multiply(yDifference).divide(xDifference).add(y0);

        return y;
    }

}
